版本控制系统(VCS)
三大要素:版本控制、主动提交、中央仓库。
每个团队成员向中央仓库主动提交自己的改动和同步别人的改动,并在需要的时候查看和操作历史版本,这就是版本控制系统。
中央式版本控制系统(Centralized VCS)
- 本地没有仓库,只有一份你签出的代码和最基本的版本信息(服务器位置以及一些关于版本号的缓存等),断网则什么都干不了,如果中央服务器出了问题,所有人都没法干活了。
分布式版本控制系统(Distributed VCS)
每个团队成员都有带版本管理的本地仓库,有所有人提交的版本记录,可以离线对仓库做一些操作,有网络时再推送到远程仓库即可。它的中央仓库虽然也保存了历史版本,但这份历史版本更多的是作为团队间的同步中转站。
大多数的操作可以在本地进行,所以速度更快,而且由于无需联网,所以即使不在公司甚至没有在联网,你也可以提交代码、查看历史,从而极大地减小了开发者的网络条件和物理位置的限制。
由于可以提交到本地,所以你可以分步提交代码,把代码提交做得更细,而不是一个提交包含很多代码,难以 review 也难以回溯。因为每个人电脑里都有完整的版本库,某个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。
Git 的一些概念
本地仓库 (Local Repository)
工作区有一个隐藏目录 .git
,这个不算工作区,而是 Git
的版本库。也叫仓库(repository),你可以简单理解成一个目录,这个目录里面的所有文件都可以被 Git
管理起来,每个文件的修改和删除都能被跟踪,以便任何时刻都可以追踪历史。
工作区(Working Directory)
就是你在电脑里能看到的目录,它保存了你当前从仓库中签出(checkout)的内容。
暂存区(Stage Area)
一个汇集所有待提交的文件改动的地方。是 .git
目录下一个叫做 index
的文件,你通过 add
指令暂存的内容都会被写进这个文件里。
偏移符号
^
:在 commit
的后面加一个或多个 ^
号,可以把 commit
往回偏移,偏移的数量是 ^
的数量。例如:master^
表示 master
指向的 commit
之前的那个 commit
。HEAD^^
表示 HEAD
所指向的 commit
往前数两个 commit
。
~
:在 commit
的后面加上 ~
号和一个数,可以把 commit
往回偏移,偏移的数量是 ~
号后面的数。例如:HEAD~5
表示 HEAD
指向的 commit
往前数 5
个 commit
。
HEAD
当前
commit
在哪里,HEAD
就在哪里,这是一个永远自动指向当前commit
的引用,所以你永远可以用HEAD
来操作当前commit
。使用
checkout
、reset
等指令手动改变当前commit
的时候,HEAD
也会一起跟过去。每次当有新的commit
的时候,工作目录会自动与最新的commit
对应,HEAD
也会转而指向最新的commit
。HEAD
是Git
中一个独特的引用,它是唯一的。HEAD
除了可以指向commit
,还可以指向一个branch
,当它指向某个branch
的时候,会通过这个branch
来间接地指向某个commit
。另外,当HEAD
在提交时自动向前移动的时候,它会像一个拖钩一样带着它所指向的branch
一起移动。
branch
是一种引用,其中
master
是Git
的默认branch
(俗称主branch
/ 主分支)。新创建的仓库是没有任何commit
的,但在它创建第一个commit
时,会把master
指向它,并把HEAD
指向master
。当使用
git clone
时,除了从远程仓库把.git
这个仓库目录下载到工作目录中,还会checkout
(签出)master
(checkout
的意思就是把某个commit
作为当前commit
,把HEAD
移动过去,并把工作目录的文件内容替换成这个commit
所对应的内容)。所以刚clone
的代码默认是处于master
分支的最新提交位置内容。branch
包含了从初始commit
到它的所有路径,而不是一条路径。并且,这些路径之间是彼此平等的。
注意:
由于 Git
中的 branch
只是一个引用,所以删除 branch
的操作也只会删掉这个引用,并不会删除任何的 commit
。(不过如果一个 commit
不在任何一个 branch
的「路径」上,或者换句话说,如果没有任何一个 branch
可以回溯到这条 commit
,那么在一定时间后,它会被 Git
的回收机制删除掉。)
比如:有分㕚的不同分支,其中的一个分支有提交过,但是从来没被合并,但是分支引用却被删除了,那么这些还没被合并的 commits
将会在一定时间后被回收掉。
Git 基本命令
clone
用于将远程仓库取下来,要克隆一个仓库,首先必须知道仓库的地址,然后使用
git clone
指令。Git
支持多种协议,包括https
,但通过ssh
支持的原生git
协议速度最快。而且,把项目clone
下来时不用https
协议,而是用ssh
协议,就不需要每次操作都要输入用户名和密码了。可以加一个额外参数来手动指定本地仓库的根目录名称。
git-practice-another 为指定目录名称
git clone git@github.com:fandazeng/git-practice.git git-practice-another
关联本地仓库和远程仓库
要关联一个远程库,使用指令
git remote add origin git@server-name:path/repo-name.git
。然后,使用指令
git push -u origin master
来推送master
分支的所有内容(第一次推送才需要用到-u
将本地的master
分支和远程的master
分支关联起来)。此后,本地提交后就可以使用指令
git push origin master
推送最新修改。
init
初始化一个 Git
仓库,使用 git init
。
add
将具体的文件改动添加进暂存区(不是文件名),在 add
之后的新改动并不会自动被添加进暂存区。
git add -A
:提交所有变化。git add -u
:提交被修改和被删除文件,不包括新文件。git add .
:提交新文件和被修改文件,不包括被删除文件。
commit
将暂存区的内容提交到仓库。 commit
的时候会进入到编辑界面:
i
(插入模式) - esc
(变回命令模式) - 大写两次 ZZ
退出并保存 )。
可以通过
-m
直接在后面添加本次提交的说明,这样就不会进入编辑模式了。有时候,提交了之后才发现改错了,又不想增加一个
commit
,可以在提交时加上--amend
参数,Git
说不会在当前commit
上增加commit
,而是会把当前commit
里的内容和暂存区里的内容合并起来后创建一个新的commit
,用这个新的commit
把当前commit
替换掉。git commit -m "reset last line" --amend
在
android studio
上,可以在提交界面右边的Amend commit
打勾。
status
用来查看工作目录当前状态的指令,可以知道哪些文件被 add
过,哪些文件被修改过等等。
log
该命令会列出你的提交历史。在 android studio
的命令行使用 git log
会处于特殊状态,可以通过双击大写的 Z
来退出。
git log -p
:查看具体commit
的改动细节。git log --stat
:查看简要统计,没有-p
那么深入细节。git log --graph
:查看分支合并图,再加上--pretty=oneline --abbrev-commit
参数,可以更简洁。
show
git show
: 查看当前commit
的改动内容。git show <commitId>
: 查看任意commitId
的改动内容。git show <tagName>
: 查看tag
(tag
和commitId
是绑定的)的改动内容。git show <commitId> <fileName>
: 查看指定commit
中的指定文件。
diff
git diff
: 显示工作区和暂存区之间的不同,「如果你现在把所有文件都add
,你会向暂存区中增加哪些内容」。git diff --staged/--cached
: 显示暂存区和上一条提交之间的不同,[如果你立即commit
,你将会提交什么」。git diff HEAD
: 显示工作目录和上一条提交之间的不同,它是上面二者的内容相加。[如果你现在把所有文件都add
然后commit
,你将会提交什么」。
注意:从来没有被 add
过 的文件并不会显示出来,因此 Git
没跟踪它们,识别不出来。
branch
git branch
:查看分支。git branch <name>
:创建分支。git checkout <name>
或者git switch <name>
:切换分支。git checkout -b <name>
或者git switch -c <name>
:创建 + 切换分支。git merge <name>
:合并某分支到当前分支。git branch -d <name>
或git branch -D <name>
:删除分支。git push origin -d <name>
:删除远程分支。
注意:
出于安全考虑,没有被合并过的 branch
在删除时会失败(因为怕你误删掉「未完成」的 branch
),如果你确定是要删除这个 branch
(例如某个未完成的功能被团队确认永久毙掉了),可以把 -d
改成 -D
,就能删除了。
stash
用于临时存放工作目录的改动。
常用场景:
你在某个分支上工作,但功能还没完成,不想提交,又必须切换到其他分支进行版本发布或 bug
修复,就可以使用 git stash
来把工作现场保存起来,之后到切换回该分支进行恢复 git stash pop
或 git stash apply
。
注意:
没有被跟踪的文件(即从来没有被
add
过的文件不会被stash
起来,因为Git
会忽略它们。如果想把这些文件也一起stash
,可以加上-u
参数,它是--include-untracked
的简写。git stash -u
每次调用
stash
都会有一条记录,可以用git stash list
查看所有的stash
记录,记录中有索引。通过
git stash pop <索引>
或者stash apply <索引>
就可以使用对应的stash
。apply
可以保留stash
空间,pop
用于恢复的同时把stash
内容也删除。为了避免pop
完产生奇怪的问题,所以优先使用apply
而不是pop
。
tag
发布一个版本时,我们通常先在版本库中打一个标签,这样就唯一确定了打标签时刻的版本。将来想取指定标签的版本,就是把那个打标签的时刻的历史版本取出来。 tag
就是一个让人容易记住的有意义的名字,它跟某个 commit
绑在一起。标签是指向 commit
的死指针,分支是指向 commit
的活指针。
git tag <tagname>
:新建一个标签,默认为HEAD
,也可以指定commit
。git tag -a <tagname> -m "说明信息"
:新建一个带说明信息的标签。git tag
:查看所有标签。git push origin <tagname>
:推送一个本地标签。git push origin --tags
:推送全部未推送过的本地标签。git tag -d <tagname>
:删除一个本地标签。git push origin :refs/tags/<tagname>
:删除一个远程标签。git show <tagname>
: 查看标签详细信息:
注意:
标签总是和某个
commit
挂钩。如果这个commit
都出现在不同分支,那么在这些分支上都可以看到这个标签。默认情况下,
git push
命令并不会上传标签到远程仓库,必须显式地推送标签到服务器上。 这个过程就像共享远程分支一样。 (在studio
上,需要在push
界面给tags
选项打勾。)git push origin [tagname]
。git tag
和git stash list
都分别列出所有分支下的所有tag
或stash
,而不管你此刻处于哪个分支。这样做是为了使用便利性,你可以在任意分支中选择想要的tag
或stash
进行处理,而不用记得指定分支下有哪些tag
或stash
。
push
把当前的分支上传到远程仓库,并把分支路径上的所有 commit
也一并上传。
常用场景:
push
到远程的内容有问题,但分支是独立开发的,不会影响到其他人,那可以用 --amend
或 reset
把写错的 commit
修改或者删除掉,然后再 push
上去就好了。不过由于你在本地对已有的 commit
做了修改,这时你再 push
就会失败,因为中央仓库包含本地没有的 commit
。但这个冲突不是因为同事 push
了新的提交,而是因为你刻意修改了一些内容,这个冲突是你预料到的,这时要选择强行 push
:git push origin branch1 -f
注意:
默认情况下,你用不加参数的
git push
只能上传那些之前从远端clone
下来或者pull
下来的分支,而如果需要push
你本地自己创建的分支,则需要手动指定目标仓库和目标分支(并且目标分支的名称必须和本地分支完全相同)。push
只上传当前分支,并不会上传HEAD
,远程仓库的HEAD
是永远指向默认分支(即master
)的。也可以让远程仓库的分支名称跟本地的不一样,(其实是将本地的提交推送到远程已有的指定分支)。
git push<远程主机名> <本地分支名> : <远程分支名>
。git push origin testBranch:test
git push origin <分支名> --force
是强制让你本地的commit
覆盖远程的commit
,要慎用。有时候在
studio
上commit
时没有记录,是因为已经用命令commit
了,可以直接push
到远程。
fetch
将远程仓库的最新内容拉到本地,用户在检查了以后决定是否合并到工作分支中。
git fetch
:将远程主机的更新全部取回本地 。git fetch <远程主机名> <分支名>
:取回指定分支的更新,比如:git fetch origin master
。
取回更新后,会返回一个 FETCH_HEAD
,指的是某个 branch
在服务器上的最新状态,我们可以在本地通过它查看刚取回的更新信息: git log -p FETCH_HEAD
。
merge
定义: 从目标 commit
和当前 commit
(即 HEAD
所指向的 commit
)分叉的位置起,把目标 commit
的路径上的所有 commit
的内容一并应用到当前 commit
,然后自动生成一个新的 commit
。(即合并自己没有的 commit
)。
冲突: 当合并不同分支时,没有修改同一部分内容,算法会自动完成。如果两个分支修改了同一部分内容,merge
的自动算法就搞不定了。这种情况称为冲突(Conflict)。
常用命令:
git merge branch
: 将branch
合并到当前分支。git merge --abort
,取消合并操作。合并的时候发生冲突,会处于一种待解决的中间状态,此时可以取消操作。
注意:
如果
HEAD
和目标commit
不存在分叉,但HEAD
落后于目标commit
,那么Git
会直接把HEAD
(以及它所指向的branch
)移动到目标commit
。这种操作叫做fast-forward
(快速前移)。经典使用场景:本地的master
没有新提交,而远端仓库中有同事提交了新内容到master
。合并分支时,加上
--no-f
f 参数就可以禁止使用fast forward
模式合并git merge --no-ff -m "merge dev no-ff" dev
,因为合并后的历史有分叉,就能看出来曾经做过合并,而fast forward
方式看不出来曾经做过合并。
pull
将远程仓库的最新内容拉下来后直接合并,即: git pull = git fetch + git merge
,这样可能会产生冲突,需要手动解决。比如: git pull origin master
。
pull
的过程可以理解为:
git fetch origin master //从远程仓库的master分支拉取最新内容
git merge FETCH_HEAD //将拉取下来的最新内容合并到当前分支
reset
常用场景:
- 想把之前的
commit
丢弃,回到某个commit
的位置。比如某个修改提交导致整个项目编译不了,想要撤销提交,可以用git reset --hard 目标commit(SHA1 值)
回到某个提交位置。
常用命令:
git reset --hard HEAD^
:HEAD^
表示你要恢复到哪个commit
。因为你要撤销最新的一个commit
,所以你需要恢复到它的父commit
,也就是HEAD^
。那么在这行之后,你的最新一条commit
就被撤销了。
reset
的本质:
reset
的本质是移动HEAD
以及它所指向的branch
到某一个commit
上,它的实质行为并不是撤销。也就是说,在版本回退之后,之前的commit
还在,可以使用git reflog
查看原先的commit SHA-1
码,然后git reset --hard commitid
回退到刚才那个commit
版本。
reset
的参数分析:
reset
指令可以重置 HEAD
和 branch
的位置,不过在重置它们的同时,对工作目录可以选择不同的操作,而对工作目录的操作的不同,就是通过 reset
后面跟的参数来确定的。
git reset --soft HEAD^
,首先移动HEAD
的指向,本质是撤销了上一次commit
命令,但是保留着add
命令。因此,上一次提交的内容会重新回到暂存区。因为当前的工作目录是在上一次提交的基础上的,所以工作目录内容不会有任何变动。此时你可以修改代码,再次add
并commit
,就能实现git commit --amend
所要做的事情了。git reset (--mixed) HEAD^
,首先移动HEAD
的指向,然后清空暂存区所有的内容。本质撤销了上一次的git add
和git commit
命令。因为当前的工作目录是在上一次提交的基础上的,所以工作目录内容也不会有任何变动。这种方式比较安全,会保留工作目录的改动。git reset --hard HEAD^
,首先移动HEAD
的指向,然后清空暂存区所有的内容,最后清空了工作目录中所有的改动。此时就将HEAD
指向的目标commit
的内容跟暂存区的内容和工作目录的内容统一了,也就是working clean
了。这种方式会比较危险,它会把当前工作的内容丢掉。
checkout
checkout
并不止可以切换分支 。git checkout branch名
的本质,其实是把 HEAD
指向指定的 branch
,然后签出这个 branch
所对应的 commit
的工作目录。所以 checkout
的目标也可以不是 branch
,而是某个 commit
。
git checkout HEAD^^
git checkout master~5
git checkout 78a4bc
git checkout 78a4bc^
上面这些操作都是可以的。也可以 checkout
文件 来达到撤销本地改动的目的(删除在 git
里面也是修改操作)。
场景一:在工作区修改了某个文件,还没有 add
,突然想撤销了,希望恢复到版本一开始的状态。
vi readme.txt
,修改了一些内容。git restore readme.txt
或使用git checkout -- readme.txt
来直接撤销所有的修改。
场景二: 新建了一个文件,并 commit
到了仓库。然后在本地不小心删除了该文件,想要恢复回来。
rm test.txt
,在本地把test.txt
文件删除掉。git checkout -- test.txt
,重新签出文件即可恢复 。
注意:
reset
会移动HEAD
分支的指向,而checkout
只会移动HEAD
自身来指向另一个分支(checkout
是带着HEAD
走,reset
是带着HEAD
和branch
一起走)。例如,假设我们有master
和develop
分支,我们现在在develop
上(所以HEAD
指向它)。 如果我们运行git reset master
,那么develop
自身现在会和master
指向同一个提交。 而如果我们运行git checkout master
的话,develop
不会移动,HEAD
自身会移动。 现在HEAD
将会指向master
。
revert
撤销某个 commit
,比如说某个 commit
新增了一些内容,而此时这些内容不需要了,那么有人可能会想到用 amend
来修改之前的 commit
,这样也可以。但是,如果新增的内容比较多,你要把所有的内容删掉再用 amend
提交就非常麻烦了。应该用 revert
,直接撤销指定 commit
的所有改动。
git revert commitID : 撤销指定的 commitID 。
git revert HEAD^^ :revert 的是倒数第二次提交 。
git revert HEAD : 反转最后一次提交。
注意:
revert
是强行在你的commits
链条上去除某一个环节(即让某一个提交的内容去除了),因此不是非常确定对后续的commit
没有任何影响的话,最好还是不要乱用。在
revert
完成之后,把新的commit
再push
上去,这个commit
的内容就被撤销了。它和前面所介绍的撤销方式相比,最主要的区别是,这次改动只是被「反转」了,并没有在历史中消失掉,你的历史中会存在两条commit
:一个原始commit
,一个对它的反转commit
。如果反转的提交后悔了,可以再次把反转的提交再反转或撤销。git reset --hard HEAD^
是撤销当前commit
,git revert HEAD
是反转最后一次提交 ,注意两者的区别。reset
其实是回滚到指定的分支,所以用HEAD^
,即回到倒数第二提交,也就是最后最新的提交去掉了。可以用
rebase -i
的方式执行drop
指令来丢弃某个提交。
reflog
用来查看 Git
仓库中的引用的移动记录。默认查看 HEAD
的移动历史,除此之外,也可以手动加上名称来查看其他引用的移动记录,git reflog master
查看 master
的移动记录。
常用场景:我在某个 commit
中删除了某个分支,在以后想要恢复。
可以使用
git reflog
查看删除分支的移动记录,最后找到对应的commitId
。签出对应
commitId
的版本,然后再创建分支,这个分支就是之前被删除的分支。比如:git checkout c08de9a
,git checkout -b branch1
。再签回
Head
的版本,就回到之前的版本了。
注意:不再被引用直接或间接指向的 commits
会在一定时间后被 Git
回收,所以使用 reflog
来找回删除的 branch
的操作一定要及时,不然有可能会由于 commit
被回收而再也找不回来。
cherry-pick
在 master
分支上修复的 bug
,想要合并到当前 dev
分支,可以用 git cherry-pick <commit>
命令,把 bug
提交的修改“复制”到当前分支,避免重复劳动。
rebase
使用场景一:
git rebase 目标基础点 。
rebase
的意思是,给你的 commit
序列重新设置基础点(也就是父 commit
)。展开来说就是,把你指定的 commit
以及它所在的 commit
串,以指定的目标 commit
为基础,依次重新提交一次。当两个不同的分支都有提交的话,此时将它们 merge
就会出现分叉的情况,如果不想出现这样的情况,可以不用 merge
,而用 rebase
。比如:
git checkout branch1
git rebase master
这两句命令的效果就是,以 master
的 HEAD
为基点,把 branch1
的提交全部移到 master
上。另外,在 rebase
之后,记得切回 master
再 merge
一下,把 master
移到最新的 commit
。
git checkout master
git merge branch1
需要说明的是,rebase
是站在需要被 rebase
的 commit
上进行操作,这点和 merge
是不同的。
使用场景二:
rebase -i:交互式 rebase
比如,我们要修改某一个 commit
,不是最新的哦(修改最新的可以用 commit --amend
),所谓「交互式 rebase
」,就是在 rebase
的操作执行之前,你可以指定要 rebase
的 commit
链中的每一个 commit
是否需要进一步修改。
比如 : git rebase -i HEAD^^
(加上 -i
就是变为交互式的)。如果没有 -i
参数的话,这种「原地 rebase
」相当于空操作,会直接结束。而在加了 -i
后,就会跳到一个新的界面。然后,选择对应的操作,操作的命令有如下种类:
pick:保留该commit(缩写:p)
reword:保留该commit,但我需要修改该commit的注释(缩写:r)
edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
squash:将该commit和前一个commit合并(缩写:s)
fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
exec:执行shell命令(缩写:x)
drop:我要丢弃该commit(缩写:d),可以用这个命令来删除之前的指定 commit ,在
Android studio 上是通过 skip 选项来移除的。
假如我们改成了 edit
并退出,此时就会进入修正状态,此时可以 add
和 commit
,在修复完成之后,就可以用rebase --continue
来继续 rebase
过程,把后面的 commit
直接应用上去。
git rebase --continue
小结:
- 使用方式是
git rebase -i 目标commit
; - 在编辑界面中指定需要操作的
commits
以及操作类型; - 操作完成之后用
git rebase --continue
来继续rebase
过程。
添加文件到 Git 仓库
分两步:
使用命令
git add <file>
,可反复多次使用来添加多个文件;使用命令
git commit -m <message>
;
文件状态
每个文件都有 "changed / unstaged"(已修改)"
,"staged"(已修改并暂存)
,"commited"(已提交)
三种状态,以及一种特殊状态 "untracked"
(未跟踪)。
详解:
- 文件从来没被
add
过,是处于特殊状态"untracked"
(未跟踪)。 - 文件被
add
了,但并没commit
,处于"staged"
(已修改并暂存)。 - 文件被
commit
了 ,处于"commited"(已提交)
。 - 文件曾经被
add
过并提交过,此时文件被修改了,处于"changed / unstaged"
(已修改),此时文件需要再次被add
,就会处于状态 2,被commit
后就会处于状态 3 。
引用的本质
所谓「引用」(
reference
),其实就是一个个的字符串。这个字符串可以是一个commit
的SHA-1
码(例:c08de9a…),也可以是一个branch
(例:ref: refs/heads/feature3)。Git
中的HEAD
和每一个branch
以及其他的引用,都是以文本文件的形式存储在本地仓库.git
目录中,而Git
在工作的时候,就是通过这些文本文件的内容来判断这些所谓的「引用」是指向谁的。
分支策略
首先,master
分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活。干活都在 dev
分支上,dev
分支是不稳定的,到某个时候,比如 1.0
版本发布时,再把 dev
分支合并到 master
上,在 master
分支发布 1.0
版本;每个人都在 dev
分支上干活,每个人都有自己的分支,时不时地往 dev
分支上合并就可以了。
feature 分支
每添加一个新功能,最好新建一个 feature
分支,在上面开发,完成后,合并,最后,删除该 feature
分支。这样做有两个好处:代码分享和一人多任务。一人多任务的场景非常简单,根本分开的功能来创建不同的 feature
分支来开发就是多任务。
代码分享使用场景:
- 分开一个新功能,新建一个
feature
分支:git switch -c feature
。 - 提交了很多代码并推送到远程:
git push origin feature
。 - 你的同事通过
fetch
或pull
把你的代码拉下来,并切换到你的功能分支。 - 这时候,你的同事就可以帮你
review
代码,如果有问题,你就反复修改即可。 - 没有问题即可合并到
master
分支了,切换到master
并pull
一下来更新最新代码,然后merge feature
分支。 - 最后,删除本地和远程分支:
git branch -d feature ,git push origin -d feature
。
注意:如果发现 feature
分支代码太烂,不准备再要了,就可以直接删除本地和远程分支,非常方便,由于分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用大写的 -D
参数。
多人协作
当你从远程仓库克隆时,实际上 Git
自动把本地的 master
分支和远程的 master
分支对应起来了,远程仓库的默认名称是 origin
。
要查看远程库的信息,用 git remote
,加上参数 -v
显示更详细的信息。
本地新建的分支如果不推送到远程,对其他人就是不可见的;
从本地推送分支,使用 git push origin branch-name
,如果推送失败,先用 git pull
抓取远程的新提交;
在本地创建和远程分支对应的分支,使用 git checkout -b branch-name origin/branch-name
,本地和远程分支的名称最好一致;
建立本地分支和远程分支的关联,使用 git branch --set-upstream branch-name origin/branch-name
;
从远程抓取分支,使用 git pull
,如果有冲突,要先处理冲突。
配置别名
--global
参数是全局参数,也就是这些命令在这台电脑的所有 Git
仓库下都有用,针对当前用户。如果不加,那只针对当前的仓库起作用。
git config --global alias.st status (为查看状态配置别名)
git config --global alias.last 'log -1' (为显示最后一次提交信息配置别名)
git config --global alias.unstage 'reset HEAD' (为暂存区的修改撤销回工作区配置别名)
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
那么配置文件放在哪里呢:
- 每个仓库的
Git
配置文件都放在.git/config
文件中 - 当前用户的
Git
配置文件放在用户主目录下的一个隐藏文件.gitconfig
中 - 我们可以直接在配置文件中进行配置
基本命令操作步骤演示
创建仓库
mkdir git-learning
,创建一个名为git-learning
的文件夹。cd git-learning
,切换到该文件夹目录。pwd
,该命令可以显示当前目录,比如(/e/2020_mine_repo/git-learning
)。git init
,把这个目录变成Git
可以管理的仓库,目录下会多了一个.git
目录,该目录是Git
来跟踪管理版本库的。
把文件添加到版本库
vi readme.txt
,然后进入编辑界面,点击i
变成插入模式,写完内容后双击大写Z
,就能创建一个文件,文件一定要在版本库内。git add readme.txt
,把文件添加到暂存区。git commit -m "wrote a readme file"
,把文件提交到仓库。
修改文件再添加到版本库
vi readme.txt
,更改内容。- 用
git status
看看仓库当前的状态。 - 用
git diff readme.txt
,查看一下修改了什么内容。 - 用
git add readme.txt
,添加到暂存区。 - 用
git status
再看看仓库当前的状态。 - 用
git commit -m "add distributed"
,提交到仓库。 - 用
git status
再看看仓库当前的状态。
对文件多次修改的操作演示
vi readme.txt
,编辑内容。cat readme.txt
,看一下内容。git add readme.txt
,添加到暂存区。git status
,查看一下状态。vi readme.txt
,再编辑一下内容。git commit -m "git tracks changes"
,提交到仓库。git status
,查看一下状态。git diff HEAD -- readme.txt
,查看当前文件和仓库里最新版本的区别。- 再次
add
和commit
第二次修改的内容。
查看文件状态的操作演示
vi readme.txt
,修改内容。vi LICENSE.txt
,新增一个文件。ls -ah
,可以查看一下所有的文件,-ah
是把隐藏文件都查看,默认不查看。git status
,查看一下状态。git add .
,该指令可以把所有的修改一次性add
到暂存区。git status
再查看一下文件状态。git commit -m "understand how stage works"
,提交到仓库。git status
再查看一下文件状态。此时nothing to commit, working tree clean
。
版本回退
vi readme.txt
,更改内容,然后add
和commit
。git log
,查看历史记录,也可以用git log --pretty=oneline
来输出更少的内容。git reset --hard HEAD^
,回退到上一个版本。cat readme.txt
,该命令可以查看一下文件的内容,回退到了上一版本的内容。- 再用
log
命令查看一下历史记录,只能看到当前的版本和以前的版本信息,最新的信息看不到了。 - 如果再想回到之前的版本,可以用
reflog
命令,来查看一下仓库的移动记录,默认是查看HEAD
的。 - 找到之前的
commitId
(比如是9360cd0
) ,然后git reset --hard 9360cd0
,回到之前的版本。
小结:
HEAD
指向的版本就是当前版本,因此,Git
允许我们在版本的历史之间穿梭,使用命令 git reset --hard commit_id
。穿梭前,用git log
可以查看提交历史,以便确定要回退到哪个版本。要重返未来,用 git reflog
查看命令历史,以便确定要回到未来的哪个版本。